/*
 *  ckr_spec_basic_sequence_and_failure.cpp
 *  ckrSpec
 *
 *  Created by C. Keith Ray on 2006.11.05.
 *  Copyright 2006 __MyCompanyName__. All rights reserved.
 *
 */

#import "ckr_spec_cpp_specs.h"
#import "ckr_spec_cpp.h"

#include <iostream>
#include <sstream>
#include <vector>
#include <deque>

#include <assert.h>

using namespace std;
using namespace ckr;

// ------------------------------------------------------------------

BEGIN_CONTEXT(Setup_spec_teardown_recorder)

	std::string log;

	explicit Setup_spec_teardown_recorder()
		: Context( "Setup_spec_teardown_recorder", __LINE__, __FILE__ )
	{
	}

	virtual void setup()
	{
		log += "setup;";
	}

	virtual void spec()
	{
		log += "spec;";
	}

	virtual void teardown()
	{
		log += "teardown;";
	}

END_CONTEXT;

SPECIFY_(A_context_should_run_setup_spec_teardown_in_order)
{
	Setup_spec_teardown_recorder recorder;
	SpecListener listener;

	recorder.run_spec( & listener ); // in normal use...  run_spec( cout ) or run_spec( cerr );

	VALUE(recorder.log).SHOULD_EQUAL( "setup;spec;teardown;" );
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class Setup_exception_thrower : public Setup_spec_teardown_recorder
{
public:
	
	Setup_exception_thrower()
		: Setup_spec_teardown_recorder( "Setup_exception_thrower" )
	{
	}

	virtual void setup()
	{
		log += "setup;";
		throw std::exception();
	}
};

SPECIFY_(should_catch_std_exception_thrown_in_setup)
{
	THIS_CODE

		Setup_exception_thrower thrower;
	
		SpecListener listener;
		thrower.run_spec( & listener );
	
	SHOULD_NOT_THROW

	// we only want to catch std::exception and subclasses of std::exception. 
	// We don't want to do catch(...) because 
	// (1) on certain systems, that catches run-time errors that the
	// programmer + IDE should handle [e.g., address-undefined errors are bugs that
	// the programmer should see, not have hidden by a spec-framework.]
	// and (2) we want the programmer to throw subclasses of std::exception, 
	// and discourage the programmer from throwing strings, ints, or other 
	// basic types.
}

SPECIFY_(should_not_call_spec_and_teardown_when_exception_thrown_in_setup)
{
	Setup_exception_thrower thrower;
	
	SpecListener listener;
	thrower.run_spec( & listener );
	
	VALUE(thrower.log).should_be == "setup;";
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class Spec_exception_thrower : public Setup_spec_teardown_recorder
{
public:
	
	Spec_exception_thrower()
		: Setup_spec_teardown_recorder( "Spec_exception_thrower" )
	{
	}

	virtual void spec()
	{
		log += "spec;";
		throw std::exception();
	}
};

SPECIFY_(should_catch_std_exception_thrown_in_spec)
{
	THIS_CODE

		Spec_exception_thrower thrower;
		
		SpecListener listener;
		thrower.run_spec( & listener );
	
	SHOULD_NOT_THROW

	// Later, we have to decide whether reporting spec violations should be
	// via exceptions (advantage: fewer error messages) or not (compatability
	// with mixed objective-c/c++ code - outside of ObjC 2.0, their exception
	// mechanisms are not compatible ).
}

SPECIFY_(should_call_teardown_when_exception_thrown_in_spec)
{
	Spec_exception_thrower thrower;
	
	SpecListener listener;
	thrower.run_spec( & listener );
	
	VALUE(thrower.log).should_be == "setup;spec;teardown;";
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

BEGIN_CONTEXT(Teardown_exception_thrower)

	Teardown_exception_thrower()
		: Context( "Teardown_exception_thrower", __LINE__, __FILE__ )
	{
	}

	virtual void setup()
	{
	}
	virtual void spec()
	{
	}
	virtual void teardown()
	{
		throw std::exception();
	}

END_CONTEXT;

SPECIFY_(should_catch_std_exception_thrown_in_teardown)
{
	THIS_CODE

		Teardown_exception_thrower thrower;
		
		SpecListener listener;
		thrower.run_spec( & listener );
	
	SHOULD_NOT_THROW
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

SPECIFY_(context_should_report_setup_exceptions_to_outStream)
{	
	Setup_exception_thrower thrower;
	
	RecordingFailuresSpecListener listener;
	thrower.run_spec( & listener );

	std::exception ex;

	std::string expected( "Exception thrown in setup of 'Setup_exception_thrower', ex.what = '" );
	expected += ex.what();
	expected += "' FAILED in Setup_exception_thrower; line";

	std::string actual = listener.logStr();
	std::string::size_type foundPos = actual.find( expected, 0 );

	VALUE(foundPos).should_be != std::string::npos;
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

SPECIFY_(context_should_report_spec_exceptions_to_outStream)
{
	Spec_exception_thrower thrower;

	RecordingFailuresSpecListener listener;
	thrower.run_spec( & listener );

	std::exception ex;

	std::string expected( "Exception thrown in spec of 'Spec_exception_thrower', ex.what = '" );
	expected += ex.what();
	expected += "' FAILED in Spec_exception_thrower; line";

	std::string actual = listener.logStr();

	std::string::size_type foundPos = actual.find( expected, 0 );

	VALUE(foundPos).should_be != std::string::npos;
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

SPECIFY_(context_should_report_teardown_exceptions_to_outStream)
{
	Teardown_exception_thrower thrower;

	RecordingFailuresSpecListener listener;
	thrower.run_spec( & listener );

	std::exception ex;

	std::string expected( "Exception thrown in teardown of 'Teardown_exception_thrower', ex.what = '" );
	expected += ex.what();
	expected += "' FAILED in Teardown_exception_thrower; line";

	std::string actual = listener.logStr();

	std::string::size_type foundPos = actual.find( expected, 0 );

	VALUE(foundPos).should_be != std::string::npos;
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// whoops... I forgot the simplest way for the client of ckr::spec to provoke a failure... calling "fail".

DECLARE_SPEC(Fail_context);

void Fail_context::spec()
{
	FAIL( "user-specified failure message" ); // intentional failure.
}

SPECIFY_(fail_reports_failure)
{
	Fail_context aContext;

	RecordingFailuresSpecListener listener;
	aContext.run_spec( & listener );

	const std::string expected( "user-specified failure message FAILED in Fail_context; line " );
	std::string actual = listener.logStr();

	std::string::size_type foundPos = actual.find( expected, 0 );

	VALUE(foundPos).should_be != std::string::npos;
}

// now let's refactor to eliminate the duplciation in Context::fail and Context::Int_value_temporary::SHOULD_EQUAL
// (the 
//			(*caller.currentOutStream) << "FAILED in " 
// stuff.)

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Throws_and_catches_context);

void Throws_and_catches_context::spec()
{
	THIS_CODE
		throw std::exception();
	SHOULD_THROW(std::exception); // no output expected
}

SPECIFY_(should_catch_std_exception_in_should_throw)
{
	Throws_and_catches_context thrower;
	
	RecordingFailuresSpecListener listener;
	thrower.run_spec( & listener );

	std::string actual = listener.logStr();
	std::string expected; // empty string

	VALUE(expected).should_be == actual;
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Does_not_throw_context);

void Does_not_throw_context::spec()
{
	THIS_CODE
		int a = 0;
		a = a;
	SHOULD_THROW(std::exception); // failure output expected
}

SPECIFY_(no_exception_caught_in_should_throw_should_report_failure)
{
	Does_not_throw_context thrower;
	
	RecordingFailuresSpecListener listener;
	thrower.run_spec( & listener );

	std::string actual = listener.logStr();

	std::string expected1( "code throwing 'std::exception' FAILED in Does_not_throw_context; line" );

	std::string::size_type foundPos = actual.find( expected1, 0 );
	VALUE(foundPos).should_be != std::string::npos;
}

//  ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

DECLARE_SPEC(Throws_in_should_not_throw_context);

void Throws_in_should_not_throw_context::spec()
{
	THIS_CODE
		int a = 0;
		a = a;
		throw std::exception();
	SHOULD_NOT_THROW; // failure output expected
}

SPECIFY_(exception_thrown_in_should_not_throw_should_report_failure)
{
	Throws_in_should_not_throw_context thrower;
	
	RecordingFailuresSpecListener listener;
	thrower.run_spec( & listener );

	std::string actual = listener.logStr();

	std::string expected1( "should not throw 'St9exception' FAILED in Throws_in_should_not_throw_context; line" );

	std::string::size_type foundPos = actual.find( expected1, 0 );
	VALUE(foundPos).should_be != std::string::npos;
}

